package blackstar.performance;

import java.io.File;
import java.io.FileFilter;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Logger;

import blackstar.performance.exception.PerformanceException;
import blackstar.performance.id.ID;

/**
 * @author RayWoo (ayufox@gmail.com)
 * 
 */
public class PerformanceAgent implements PerformanceConfigConstants
{
	private final static Logger LOGGER = Logger
			.getLogger(PerformanceAgent.class.getSimpleName());

	private static Properties config = new Properties();

	private static boolean isTransformerRegister = false;
	private static PerformanceClassFileTransformer transformer;

	private static Set<ClassLoader> excludeClassLoaders = new HashSet<ClassLoader>();
	static
	{
		ClassLoader classLoader = PerformanceAgent.class.getClassLoader()
				.getParent();
		while (classLoader != null)
		{
			excludeClassLoaders.add(classLoader);
			classLoader = classLoader.getParent();
		}
	}

	public static void premain(String agentArgs, Instrumentation inst)
			throws Exception
	{
		LOGGER.info("premain[" + agentArgs + "]");
		initConfig(agentArgs);
		registIDMBean();
		registPerformanceTransformer(inst);
	}

	public static void agentmain(String agentArgs, Instrumentation inst)
			throws Exception
	{
		LOGGER.info("agentmain[" + agentArgs + "]");

		Properties properties = new Properties();
		parsetArgs(properties, agentArgs);

		boolean reweaving = (!"false".equals(properties.get("reweaving")))
				|| (transformer == null);

		if (transformer == null)
		{
			initConfig(agentArgs);
			registIDMBean();
			registPerformanceTransformer(inst);
		}

		if (!reweaving || transformer == null)
		{
			LOGGER.info("ReWeaving Unneeded");

			return;
		}

		if (transformer.reloadWeavingConfig())
		{
			if (!isTransformerRegister)
			{
				LOGGER.info("registerTransformer");
				inst.addTransformer(transformer);
			}

			LOGGER.info("reloadWeavingConfig Success");

			Class[] classes = inst.getAllLoadedClasses();
			List<Class> retransformClasses = new ArrayList<Class>();
			for (Class clazz : classes)
			{
				if (inst.isModifiableClass(clazz)
						&& clazz.getClassLoader() != null
						&& !excludeClassLoaders
								.contains(clazz.getClassLoader())
						&& transformer.testWeaving(clazz))
				{
					LOGGER.info("retransformClass:" + clazz.getName());
					retransformClasses.add(clazz);
				}
			}

			transformer.clearPreloadedClassNames();

			inst.retransformClasses(retransformClasses.toArray(new Class[0]));
		} else
		{
			LOGGER.info("reloadWeavingConfig Failed");
		}
	}

	private static void registPerformanceTransformer(Instrumentation inst)
	{
		if ("true".equals(config.getProperty(ENABLED)))
		{
			LOGGER.info("Permance Monitor is enabled");

			String id = config.getProperty(ID_CONFIG);

			String libpath = config.getProperty(PERFORMANCE_LIB_PATH);
			File libDir = new File(libpath);
			if (!libDir.exists() || !libDir.isDirectory())
			{
				throw new PerformanceException("LibPath[" + libpath
						+ "] Not Exist Or Directory");
			}

			String configPath = config.getProperty(PERFORMANCE_CONFIG_PATH);
			File configDir = new File(configPath);

			String transformerClassName = config
					.getProperty(TRANSFORM_CLASSNAME_CONFIG_NAME);
			if (transformerClassName == null
					|| transformerClassName.length() == 0)
			{
				transformerClassName = DEFAULT_TRANSFORM_CLASS;
			}

			LOGGER.info("Transformer Class[" + transformerClassName + "]");

			registerTransformer(id, transformerClassName, libDir, configDir,
					inst);

			LOGGER.info("Permance Monitor init success!");
		} else
		{
			LOGGER.info("Permance Monitor is disabled");
		}
	}

	private static void registerTransformer(String id,
			String transformerClassName, File libDir, File configDir,
			Instrumentation inst)
	{
		try
		{
			List<URL> urls = new ArrayList<URL>();

			File[] files = libDir.listFiles(new FileFilter()
			{
				public boolean accept(File pathname)
				{
					return pathname.getName().endsWith(".jar");
				}
			});

			for (File file : files)
			{
				if (file.canRead())
				{
					URL url = file.toURI().toURL();
					urls.add(url);
					LOGGER.fine("add url[" + url + "]");
					url = null;
				}
			}

			if (configDir.exists() && configDir.isDirectory())
			{
				urls.add(configDir.getParentFile().toURI().toURL());
			}

			URL[] urlArray = urls.toArray(new URL[urls.size()]);
			URLClassLoader classLoader = new URLClassLoader(urlArray, Thread
					.currentThread().getContextClassLoader());

			Class clazz = classLoader.loadClass(transformerClassName);
			Constructor<PerformanceClassFileTransformer> constructor = clazz
					.getConstructor(Properties.class, Set.class);
			transformer = constructor.newInstance(config, excludeClassLoaders);
			if (transformer.reloadWeavingConfig())
			{
				LOGGER.info("reloadWeavingConfig Success");
				inst.addTransformer(transformer, true);
				isTransformerRegister = true;
			} else
			{
				LOGGER.info("reloadWeavingConfig Failed. No Transformer");
			}
		} catch (MalformedURLException e)
		{
			throw new PerformanceException("Error", e);
		} catch (Exception e)
		{
			throw new PerformanceException("Error When Create Transformer", e);
		}
	}

	private static void registIDMBean()
	{
		String id = config.getProperty(ID_CONFIG);

		MBeanRegister.registMBean(ID_BEAN_NAME, new ID(id));
	}

	private static void parsetArgs(Properties properties, String agentArgs)
	{
		String[] args = agentArgs.split(";");
		for (String arg : args)
		{
			arg = arg.trim();
			if (arg.length() > 0)
			{
				String[] strArray = arg.split("=");
				if (strArray.length == 2)
				{
					properties.put(strArray[0], strArray[1].trim());
				} else
				{
					LOGGER.warning("Error Agent Argment Format : " + agentArgs);
				}
				strArray = null;
			}
		}

	}

	private static void initConfig(String agentArgs)
	{
		parsetArgs(config, agentArgs);
		String id = config.getProperty(ID_CONFIG);

		if (isEmpty(id))
		{
			throw new PerformanceException("ID Argment is needed");
		}

		boolean enabled = "true".equals(config.getProperty(ENABLED));
		if (enabled)
		{
			String agentHome = getProperty(AGENT_HOME);
			LOGGER.info("AGENT_HOME:" + agentHome);
			if (isEmpty(agentHome))
			{
				throw new PerformanceException("Agent Home Is Empty");
			}
			config.put(AGENT_HOME, agentHome);

			String defaultLibPath = agentHome + File.separator + "lib"
					+ File.separator + "performance" + File.separator;
			String libpath = getProperty(PERFORMANCE_LIB_PATH, defaultLibPath);
			config.put(PERFORMANCE_LIB_PATH, libpath);
			LOGGER.info("LIB_PATH:" + libpath);

			String defaultConfigPath = agentHome + File.separator + "config"
					+ File.separator + "performance" + File.separator;
			String configPath = getProperty(PERFORMANCE_CONFIG_PATH,
					defaultConfigPath);
			config.put(PERFORMANCE_CONFIG_PATH, configPath);
			LOGGER.info("CONFIG_PATH:" + configPath);

			String defaultLogPath = agentHome + File.separator + "log"
					+ File.separator + id + File.separator + "performance"
					+ File.separator;

			String logPath = getProperty(PERFORMANCE_LOG_PATH, defaultLogPath);
			config.put(PERFORMANCE_LOG_PATH, logPath);
			LOGGER.info("LOG_PATH:" + logPath);
		}
	}

	public static String getProperty(String name)
	{
		return getProperty(name, null);
	}

	public static String getProperty(String name, String defaultValue)
	{
		String value = config.getProperty(name);

		if (!isEmpty(value))
		{
			return value;
		}

		value = System.getProperty(name);

		if (!isEmpty(value))
		{
			return value;
		}

		value = System.getenv(name);
		if (!isEmpty(value))
		{
			return value;
		}

		return defaultValue;
	}

	private static boolean isEmpty(String value)
	{
		return value == null || value.length() == 0;
	}
}